The Art of Writing Efficient Programs by Fedor G. Pikus
Author:Fedor G. Pikus [Fedor G. Pikus]
Language: eng
Format: epub
Publisher: Packt Publishing
Published: 2021-10-22T00:00:00+00:00
Counters and accumulators
One of the simplest thread-safe objects is a humble counter or its more general form, an accumulator. The counter simply counts some events that can occur on any of the threads. All threads may need to increment the counter or access the current value, so there is potential for a race condition.
To be of value, we need the strong thread safety guarantee here: the weak guarantee is trivial; reading a value that nobody is changing is always thread-safe. We have already seen the available options for the implementation: a lock of some kind, an atomic operation (when one is available), or a lock-free CAS loop.
The performance of a lock varies with the implementation, but a spinlock is, in general, preferred. The wait time for a thread that did not get immediate access to the counter is going to be very short. So, it does not make sense to incur the cost of putting the thread to sleep and waking it up later. On the other hand, the amount of CPU time wasted because of the busy waiting (polling the spinlock) is going to be negligible, most likely just a few instructions.
The atomic instruction delivers good performance, but the choice of operations is rather limited: in C++, you can atomically add to an integer but not, for example, multiply it. This is enough for a basic counter but may be insufficient for a more general accumulator (the accumulating operation does not have to be limited to a sum). However, if one is available, you just cannot beat the simplicity of an atomic operation.
The CAS loop can be used to implement any accumulator, regardless of the operation we need to use. However, on most modern hardware, it is not the fastest option and is outperformed by a spinlock (see Figure 6.2).
The spinlock can be further optimized for the case when it is used to access a single variable or a single object. Instead of a generic flag, we can make the lock itself be the only reference to the object it is guarding. The atomic variable is going to be a pointer, not an integer, but otherwise, the locking mechanism remains unchanged. The lock() function is non-standard because it returns the pointer to the counter:
template <typename T>
class PtrSpinlock {
public:
explicit PtrSpinlock(T* p) : p_(p) {}
T* lock() {
while (!(saved_p_ =
p_.exchange(nullptr, std::memory_order_acquire))) {}
}
void unlock() {
p_.store(saved_p_, std::memory_order_release);
}
private:
std::atomic<T*> p_;
T* saved_p_ = nullptr;
};
Compared to the earlier implementation of the spinlock, the meaning of the atomic variable is "inverted:" the lock is available if the atomic variable p_ is not null, otherwise it is taken. All the optimizations we have done for the spinlock are applicable here as well and look exactly the same, so we are not going to repeat them. Also, to be complete, the class needs a set of deleted copy operations (locks are non-copyable). It may be movable if the ability to transfer the lock and the responsibility to release it to another object is desirable.
Download
This site does not store any files on its server. We only index and link to content provided by other sites. Please contact the content providers to delete copyright contents if any and email us, we'll remove relevant links or contents immediately.
Exploring Deepfakes by Bryan Lyon and Matt Tora(7750)
Robo-Advisor with Python by Aki Ranin(7648)
Offensive Shellcode from Scratch by Rishalin Pillay(6117)
Microsoft 365 and SharePoint Online Cookbook by Gaurav Mahajan Sudeep Ghatak Nate Chamberlain Scott Brewster(5052)
Ego Is the Enemy by Ryan Holiday(4960)
Management Strategies for the Cloud Revolution: How Cloud Computing Is Transforming Business and Why You Can't Afford to Be Left Behind by Charles Babcock(4440)
Python for ArcGIS Pro by Silas Toms Bill Parker(4192)
Elevating React Web Development with Gatsby by Samuel Larsen-Disney(3900)
Machine Learning at Scale with H2O by Gregory Keys | David Whiting(3640)
Learning C# by Developing Games with Unity 2021 by Harrison Ferrone(3286)
Speed Up Your Python with Rust by Maxwell Flitton(3232)
Liar's Poker by Michael Lewis(3228)
OPNsense Beginner to Professional by Julio Cesar Bueno de Camargo(3195)
Extreme DAX by Michiel Rozema & Henk Vlootman(3175)
Agile Security Operations by Hinne Hettema(3125)
Linux Command Line and Shell Scripting Techniques by Vedran Dakic and Jasmin Redzepagic(3110)
Essential Cryptography for JavaScript Developers by Alessandro Segala(3083)
Cryptography Algorithms by Massimo Bertaccini(3002)
AI-Powered Commerce by Andy Pandharikar & Frederik Bussler(2984)
